Funciones
La creación de funciones propias nos permite automatizar todas aquellas partes del código que se repiten mucho. Una vez diseñadas, funcionan igual que cualquier comando.
Por ejemplo, podemos definir la suma de dos elementos como
rr suma <- function(valor1, valor2) { valor1+valor2 }
suma(5,6)
[1] 11
Obviamente las funciones no son sólo para variables numéricas. Por ejemplo, podemos pegar dos strings con una flecha en el medio
rr funcion_prueba <- function(parametro1,parametro2) { paste(parametro1, parametro2, sep = <–> ) }
funcion_prueba(parametro1 = ver, parametro2 = pasa)
[1] \A ver <--> Que pasa\
También podemos asignar un valor por default para los parametros en caso de que el usuario no defina su valor al utilizar la función.
rr Otra_funcion_prueba <- function(parametro1 ,parametro2 = default) { paste(parametro1, parametro2, sep = <–> )
} Otra_funcion_prueba(parametro1 = 1 )
[1] \Valor 1 <--> String default\
Las funciones que creamos nosotros permanecen en el ambiente de R temporariamente. Cuando removemos los objetos del ambiente, la función deja de existir. Por ende, debemos incorporarla en cada uno de los scripts en la cual la necesitemos. Una buena práctica, es incorporar nuestras funciones útiles al comienzo de cada script junto a la carga de las librerías.
Vale mencionar que lo que ocurre en una función, queda en la función excepto que explícitamente pidamos que devuelva el resultado, con el comando print().
Las funciones siempre devuelven el último objeto que se crea en ellas, o si explicitamente se utiliza el comando return()
PURRR
MAP es la forma tidy de hacer loops. Además de ser más prolijo el código, es mucho más eficiente.
La función map toma un input, una función para aplicar, y alguna otra cosa (por ejemplo parametros que necesite la función)
- map(.x, .f, …)
- map(VECTOR_O_LIST_INPUT, FUNCTION_A_APLICAR, OTROS_OPCIONALES)
Usamos map2 cuando tenemos que pasar dos input, que se aplican sobre una función:
- map2(.x, .y, .f, …)
- map2(INPUT_UNO, INPUT_DOS, FUNCTION_A_APLICAR, OTROS_OPCIONALES)
Si tenemos más de dos…
- pmap(.l, .f, …)
- pmap(VECTOR_O_LIST_INPUT, FUNCTION_A_APLICAR, OTROS_OPCIONALES)
Por ejemplo. Si queremos utilizar la función prueba sobre los datos del dataframe ABC_123
rr ABC_123 <- data.frame(Letras = LETTERS[1:20],Num = 1:20) funcion_prueba
function(parametro1,parametro2) {
paste(parametro1, parametro2, sep = \ <--> \)
}
Si el resultado que queremos es que junte cada fila, necesitamos pasarle dos parámetros: utilizamos map2()
rr resultado <- map2(ABC_123\(Letras,ABC_123\)Num,funcion_prueba) resultado[1:3]
[[1]]
[1] \A <--> 1\
[[2]]
[1] \B <--> 2\
[[3]]
[1] \C <--> 3\
La salida de los map() es una lista, no un vector, por lo que si lo metemos dentro de un dataframe se vería así:
rr ABC_123 %>% mutate(resultado= map2(Letras,Num,funcion_prueba)) r NA
al ponerlo dentro del dataframe desarma la lista y guarda cada elemento por separado. La magia de eso es que podemos guardar cualquier cosa en el dataframe no sólo valores, sino también listas, funciones, dataframes, etc.
Si queremos recuperar los valores originales en este caso podemos usar unlist()
rr
resultado[1:3] %>% unlist()
[1] \A <--> 1\ \B <--> 2\ \C <--> 3\
rr ABC_123 %>% mutate(resultado= unlist(map2(Letras,Num,funcion_prueba))) r NA
Si lo que queríamos era que la función nos haga todas las combinaciones de letras y número, entonces lo que necesitamos es pasarle el segúndo parametro como algo fijo, poniendolo después de la función.
rr map(ABC_123\(Letras,funcion_prueba,ABC_123\)Num)[1:2]
[[1]]
[1] \A <--> 1\ \A <--> 2\ \A <--> 3\ \A <--> 4\ \A <--> 5\ \A <--> 6\ \A <--> 7\ \A <--> 8\ \A <--> 9\ \A <--> 10\
[11] \A <--> 11\ \A <--> 12\ \A <--> 13\ \A <--> 14\ \A <--> 15\ \A <--> 16\ \A <--> 17\ \A <--> 18\ \A <--> 19\ \A <--> 20\
[[2]]
[1] \B <--> 1\ \B <--> 2\ \B <--> 3\ \B <--> 4\ \B <--> 5\ \B <--> 6\ \B <--> 7\ \B <--> 8\ \B <--> 9\ \B <--> 10\
[11] \B <--> 11\ \B <--> 12\ \B <--> 13\ \B <--> 14\ \B <--> 15\ \B <--> 16\ \B <--> 17\ \B <--> 18\ \B <--> 19\ \B <--> 20\
En este caso, el map itera sobre cada elemento de letras, y para cada elemento i hace funcion_prueba(i,ABC$Num) y guarda el resultado en la lista
si lo queremos meter en el dataframe
rr ABC_123 %>% mutate(resultado= map(Letras,funcion_prueba,Num))
Ahora cada fila tiene un vector de 20 elementos guardado en la columna resultado
Iterando en la EPH
Lo primero que necesitamos es definir un vector o lista sobre el que iterar.
Por ejemplo, podemos armar un vector con los path a las bases individuales, con el comando fs::dir_ls
rr
bases_individuales_path <- dir_ls(path = ‘../Fuentes/’, regexp= ‘individual’) bases_individuales_path
../Fuentes/usu_individual_t216.txt ../Fuentes/usu_individual_t316.txt ../Fuentes/usu_individual_t416.txt
Luego, como en la función que usamos para leer las bases definimos muchos parametros, nos podemos armar una función wrapper que sólo necesite un parámetro, y que simplifique la escritura del map
rr
leer_base_eph <- function(path) { read.table(path,sep=;, dec=
```
rr bases_df
El resultado es un DF donde la columna base tiene en cada fila, otro DF con la base de la EPH de ese período. Esto es lo que llamamos un nested DF o dataframe nesteado pa les pibes.
Si queremos juntar todo, podemos usar unnest()
rr bases_df <- bases_df %>% unnest() bases_df
¿Qué pasa si los DF que tenemos nesteados no tienen la misma cantidad de columnas?
Esto mismo lo podemos usar para fragmentar el datastet por alguna variable, con el group_by()
rr bases_df %>% group_by(REGION) %>% nest() r NA
Así, para cada región tenemos un DF.
¿ De qué sirve todo esto?
No todo en la vida es un Dataframe. Hay estucturas de datos que no se pueden normalizar a filas y columnas. En esos casos recurríamos tradicionalmente a los loops. Con MAP podemos tener los elementos agrupados en un sólo objeto y aún conservar sus formas diferentes.
Ejemplo. Regresión lineal
Si bien no nos vamos a meter en el detalle del modelo lineal, es útil usarlo como ejemplo de lo que podemos hacer con MAP.
Planteamos el modelo \[
P21 = \beta_0 + \beta_1*CH04 + \beta_2*CH06
\] Osea, un modleo que explica el ingreso según sexo y edad
rr
lmfit <- lm(P21~factor(CH04)+CH06,data = bases_df)
summary(lmfit)
Call:
lm(formula = P21 ~ factor(CH04) + CH06, data = bases_df)
Residuals:
Min 1Q Median 3Q Max
-7978 -3521 -1854 723 393800
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 2532.2754 32.5575 77.78 <2e-16 ***
factor(CH04)2 -2178.1051 31.6989 -68.71 <2e-16 ***
CH06 55.5641 0.7113 78.11 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 6658 on 177512 degrees of freedom
Multiple R-squared: 0.05415, Adjusted R-squared: 0.05414
F-statistic: 5082 on 2 and 177512 DF, p-value: < 2.2e-16
(al final de la clase podemos charlar sobre los resultados, si hay interés :-) )
De forma Tidy, la librería broom nos da los resultados en un DF.
rr broom::tidy(lmfit)
Si lo queremos hacer por region
Loopeando
rr
resultados <- tibble()
for (region in unique(bases_df$REGION)) {
data <- bases_df %>% filter(REGION==region)
lmfit <- lm(P21~factor(CH04)+CH06,data = data)
lmtidy <- broom::tidy(lmfit) lmtidy$region <- region resultados <- bind_rows(resultados,lmtidy)
}
resultados
Usando MAP
Primero me armo una funcion que me simplifica el codigo
rr fun<-function(porcion,grupo) { broom::tidy(lm(P21~factor(CH04)+CH06,data = porcion))}
rr bases_df_lm <- bases_df %>% group_by(REGION) %>% nest() %>% mutate(lm = map(data,fun)) bases_df_lm r bases_df_lm %>% unnest(lm)
O incluso más facil, utilizando group_modify (que es un atajo que solo acepta DF)
rr bases_df %>% group_by(REGION) %>% group_modify(fun)
Pero MAP sirve para operar con cualquier objeto de R.
Por ejemplo podemos guardar el objeto S3:lm que es la regresion lineal entrenada. Ese objeto no es ni un vector, ni una lista, ni un DF. No es una estructura de datos, sino que es algo distinto, con propiedades como predict() para predecir, el summary() que vimos, etc.
rr fun<-function(porcion,grupo) { lm(P21~factor(CH04)+CH06,data = porcion)}
bases_df %>% group_by(REGION) %>% nest() %>%
mutate(lm = map(data,fun))
Walk
Las funciones Walk Tienen la misma forma que los map, pero se usan cuando lo que queremos iterar no genera una salida, sino que nos interesan los efectos secundarios que generan.
rr map2(ABC_123\(Letras,ABC_123\)Num,funcion_prueba)[1:3]
[[1]]
[1] \A <--> 1\
[[2]]
[1] \B <--> 2\
[[3]]
[1] \C <--> 3\
rr walk2(ABC_123\(Letras,ABC_123\)Num,funcion_prueba)
rr
imprimir_salida <- function(x,y){ print(funcion_prueba(x,y)) }
walk2(ABC_123\(Letras,ABC_123\)Num,imprimir_salida)
[1] \A <--> 1\
[1] \B <--> 2\
[1] \C <--> 3\
[1] \D <--> 4\
[1] \E <--> 5\
[1] \F <--> 6\
[1] \G <--> 7\
[1] \H <--> 8\
[1] \I <--> 9\
[1] \J <--> 10\
[1] \K <--> 11\
[1] \L <--> 12\
[1] \M <--> 13\
[1] \N <--> 14\
[1] \O <--> 15\
[1] \P <--> 16\
[1] \Q <--> 17\
[1] \R <--> 18\
[1] \S <--> 19\
[1] \T <--> 20\
Eso que vemos es el efecto secundario dentro de la función (imprimir)
Discusión.
Cuando usar estas herramientas?
A lo largo del curso vimos diferentes técnicas para manipulación de datos. En particular, la librería dplyr nos permitía fácilmente modificar y crear nuevas variables, agrupando. Cuando usamos dplyr y cuando usamos purrr.
- Si trabajamos sobre un DF simple, sin variables anidadas (lo que conocíamos hasta hoy) podemos usar
dplyr
Si queremos trabajar con DF anidados, con cosas que no son DF, o si el resultado de la operación que vamos a realizar a nivel file es algo distinto a un valor único, nos conviene usar map y purrr
Las funciones walk son útiles por ejemplo para escribir archivos en disco de forma iterativa. Algo que no genera una salida
LS0tCnRpdGxlOiAiVXRpbGl6YWNpw7NuIGRlbCBsZW5ndWFqZSBSIHBhcmEgYXBsaWNhY2nDs24gZW4gbGEgRW5jdWVzdGEgUGVybWFuZW50ZSBkZSBIb2dhcmVzIgpzdWJ0aXRsZTogIkNsYXNlIDggLSBQcm9ncmFtYWNpb24gRnVuY2lvbmFsIgpkYXRlOiAiMjQvMDkvMjAxOSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCipMYXMgbm90YXMgZGVsIHByZXNlbnRlIGN1cnNvIGZ1ZXJvbiBlbGFib3JhZGFzIG9yaWdpbmFsbWVudGUgcG9yIERpZWdvIEtvemxvd3NraSB5IEd1aWRvIFdla3NsZXIuIEVuIGxhcyBzdWNlc2l2YXMgbW9kaWZpY2FjaW9uZXMgY29sYWJvcmFyb246IE5hdHN1bWkgU2hva2lkYSB5IE1hdMOtYXMgTGlvbmkqICAgICAgICAgICAgICAgICAgICAgIAoKCj5SZWluaWNpYXIgUgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkob3Blbnhsc3gpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkoZnMpCmBgYAoKCiMgTG9vcHMKClVuIF9fbG9vcF9fIGVzIHVuYSBlc3RydWN0dXJhIGRlIGPDs2RpZ28gcXVlIG5vcyBwZXJtaXRlIGFwbGljYXIgaXRlcmF0aXZhbWVudGUgdW4gbWlzbW8gY29uanVudG8gZGUgY29tYW5kb3MsIHZhcmlhbmRvIGVsIHZhbG9yIGRlIHVuYSB2YXJpYWJsZS4gUG9yIGVqZW1wbG86CgpgYGB7cix3YXJuaW5nPUZBTFNFfQpmb3IoaSBpbiAxOjEwKXsKICAgcHJpbnQoaV4yKQp9CmBgYAoKRXN0byBzZSBsZWUgY29tbyA6ICJSZWNvcnJlIGNhZGEgdW5vIGRlIGxvcyB2YWxvcmVzIChpKSBkZWwgdmVjdG9yIG51bcOpcmljbyAxIGEgMTAsIHkgcGFyYSBjYWRhIHVubyBkZSBlbGxvcyBpbXByaW3DrSBlbCBjdWFkcmFkbyAoaV4yKSIuICAgICAgICAgICAgICAgICAgClVubyBwdWVkZSBlc3BlY2lmaWNhciBsYSBwYWxhYnJhIHF1ZSBkZXNlZSBxdWUgdG9tw6kgY2FkYSB1bm8gZGUgbG9zIHZhbG9yZXMgcXVlIGRlYmUgdG9tYXIuIEVuIGVsIGVqZW1wbG8gYW50ZXJpb3IgZnVlICoqaSoqLCBwZXJvIGJpZW4gcG9kcsOtYSBzZXIgbGEgIioqVmFsb3JlcyoqIiAgCgpgYGB7cn0KZm9yKFZhbG9yZXMgaW4gMToxMCl7CiAgIHByaW50KFZhbG9yZXNeMikKICAKfQpgYGAKClVuIGxvb3AgcHVlZGUgaXRlcmFyIHNvYnJlIGN1YWxxdWllciB0aXBvIGRlIHZlY3RvciwgaW5kZXBlbmRpZW50ZW1lbnRlIGRlIGxvIHF1ZSBjb250ZW5nYS4KCj4gTG9zIGxvb3BzIHNvbiB1bmEgZXN0cnVjdHVyYSBiw6FzaWNhIHF1ZSBleGlzdGVuIGVuIGN1YWxxdWllciBsZW5ndWFqZSBkZSBwcm9ncmFtYWNpw7NuLiBFbiBSIG5vIHJlY29tZW5kYW1vcyBhYnVzYXIgZGUgZWxsb3MgcG9ycXVlIGhhY2VuIHF1ZSBlbCBjw7NkaWdvIHNlYSBtw6FzIGxlbnRvLgoKCiMgRXN0cnVjdHVyYXMgQ29uZGljaW9uYWxlcwoKTGFzIF9fZXN0cnVjdHVyYXMgY29uZGljb25hbGVzX18gbm9zIHBlcm1pdGVuIGVqZWN1dGFyIHVuYSBwb3JjacOzbiBkZSBjw7NkaWdvIGVuIGNhc28gZGUgcXVlIGN1bXBsYW4gdW5hIGNvbmRpY2nDs24gbMOzZ2ljYQoKIyMgaWYKU3UgZnVuY2lvbmFtaWVudG8gZXMgZWwgc2lndWllbnRlOiAgICAgCmBgYGlmKGNvbmRpY2lvbil7Y29kaWdvIGEgZWplY3V0YXIgc2kgc2UgY3VtcGxlIGxhIGNvbmRpY2nDs259YGBgCgoKYGBge3Isd2FybmluZz1GQUxTRX0KaWYoIDIrMiA9PSA0KXsKICBwcmludCgiTWVub3MgTWFsIikKfQoKYGBgCgpgYGB7cix3YXJuaW5nPUZBTFNFfQppZiggMisyID09IDE0OC4yNCl7CiAgcHJpbnQoIlIsIHRlbmVtb3MgdW4gcHJvYmxlbWEiKQp9CgpgYGAKCgojIyBpZmVsc2UKTGEgZnVuY2nDs24gYGBgaWZfZWxzZSgpYGBgIHNpcnZlIHBhcmEgY3JlYXIgbyBtb2RpZmljYXIgZGljb3TDs21pY2FtZW50ZSB1biBvYmpldG8vdmFyaWFibGUvdmVjdG9yIGEgcGFydGlyIGRlbCBjdW1wbGltaWVudG8gZGUgdW5hIG8gbcOhcyBjb25kaWNpb25lcyBsw7NnaWNhcy4gIApTdSBmdW5jaW9uYW1pZW50byBlcyBlbCBzaWd1aWVudGU6ICAgICAgCmBgYGlmX2Vsc2UoY29uZGljaW9uLGZ1bmNpw7NuIGEgYXBsaWNhciBzaSBzZSBjdW1wbGUgbGEgY29uZGljacOzbixmdW5jacOzbiBhIGFwbGljYXIgc2kgbm8gc2UgY3VtcGxlIGxhIGNvbmRpY2nDs24pYGBgCgpgYGB7cn0KaWZfZWxzZSgyKzI9PTQsIHRydWUgPSAiSm95YSIsZmFsc2UgPSAiRXJyb3IiKQpgYGAKCiMgRnVuY2lvbmVzCgpMYSBjcmVhY2nDs24gZGUgX19mdW5jaW9uZXNfXyBwcm9waWFzIG5vcyBwZXJtaXRlIGF1dG9tYXRpemFyIHRvZGFzIGFxdWVsbGFzIHBhcnRlcyBkZWwgY8OzZGlnbyBxdWUgc2UgcmVwaXRlbiBtdWNoby4gVW5hIHZleiBkaXNlw7FhZGFzLCBmdW5jaW9uYW4gaWd1YWwgcXVlIGN1YWxxdWllciBjb21hbmRvLiAKClBvciBlamVtcGxvLCBwb2RlbW9zIGRlZmluaXIgbGEgc3VtYSBkZSBkb3MgZWxlbWVudG9zIGNvbW8KCgpgYGB7cn0Kc3VtYSA8LSBmdW5jdGlvbih2YWxvcjEsIHZhbG9yMikgewogIHZhbG9yMSt2YWxvcjIKfQoKc3VtYSg1LDYpCmBgYAoKCk9idmlhbWVudGUgbGFzIGZ1bmNpb25lcyBubyBzb24gc8OzbG8gcGFyYSB2YXJpYWJsZXMgbnVtw6lyaWNhcy4gUG9yIGVqZW1wbG8sIHBvZGVtb3MgcGVnYXIgZG9zIHN0cmluZ3MgY29uIHVuYSBmbGVjaGEgZW4gZWwgbWVkaW8KCmBgYHtyfQpmdW5jaW9uX3BydWViYSA8LSBmdW5jdGlvbihwYXJhbWV0cm8xLHBhcmFtZXRybzIpIHsKICBwYXN0ZShwYXJhbWV0cm8xLCBwYXJhbWV0cm8yLCBzZXAgPSAiIDwtLT4gIikKfQoKZnVuY2lvbl9wcnVlYmEocGFyYW1ldHJvMSA9ICJBIHZlciIsIHBhcmFtZXRybzIgPSAiUXVlIHBhc2EiKQoKYGBgCgpUYW1iacOpbiBwb2RlbW9zIGFzaWduYXIgdW4gdmFsb3IgcG9yIGRlZmF1bHQgcGFyYSBsb3MgcGFyYW1ldHJvcyBlbiBjYXNvIGRlIHF1ZSBlbCB1c3VhcmlvIG5vIGRlZmluYSBzdSB2YWxvciBhbCB1dGlsaXphciBsYSBmdW5jacOzbi4KCmBgYHtyfQpPdHJhX2Z1bmNpb25fcHJ1ZWJhIDwtIGZ1bmN0aW9uKHBhcmFtZXRybzEgLHBhcmFtZXRybzIgPSAiU3RyaW5nIGRlZmF1bHQiKSB7CiAgcGFzdGUocGFyYW1ldHJvMSwgcGFyYW1ldHJvMiwgc2VwID0gIiA8LS0+ICIpCiAgCn0KT3RyYV9mdW5jaW9uX3BydWViYShwYXJhbWV0cm8xID0gIlZhbG9yIDEgIikKCmBgYAoKTGFzIGZ1bmNpb25lcyBxdWUgY3JlYW1vcyBub3NvdHJvcyBwZXJtYW5lY2VuIGVuIGVsIGFtYmllbnRlIGRlIFIgdGVtcG9yYXJpYW1lbnRlLiBDdWFuZG8gcmVtb3ZlbW9zIGxvcyBvYmpldG9zIGRlbCBhbWJpZW50ZSwgbGEgZnVuY2nDs24gZGVqYSBkZSBleGlzdGlyLiBQb3IgZW5kZSwgZGViZW1vcyBpbmNvcnBvcmFybGEgZW4gY2FkYSB1bm8gZGUgbG9zIHNjcmlwdHMgZW4gbGEgY3VhbCBsYSBuZWNlc2l0ZW1vcy4gVW5hIGJ1ZW5hIHByw6FjdGljYSwgZXMgaW5jb3Jwb3JhciBudWVzdHJhcyBmdW5jaW9uZXMgw7p0aWxlcyBhbCBjb21pZW56byBkZSBjYWRhIHNjcmlwdCBqdW50byBhIGxhIGNhcmdhIGRlIGxhcyBsaWJyZXLDrWFzLiAgICAgICAgICAgICAgICAgICAgICAgICAKClZhbGUgbWVuY2lvbmFyIHF1ZSBfX2xvIHF1ZSBvY3VycmUgZW4gdW5hIGZ1bmNpw7NuLCBxdWVkYSBlbiBsYSBmdW5jacOzbl9fIGV4Y2VwdG8gcXVlIGV4cGzDrWNpdGFtZW50ZSBwaWRhbW9zIHF1ZSBkZXZ1ZWx2YSBlbCByZXN1bHRhZG8sIGNvbiBlbCBjb21hbmRvIGBwcmludCgpYC4gCgpMYXMgZnVuY2lvbmVzIHNpZW1wcmUgZGV2dWVsdmVuIGVsIMO6bHRpbW8gb2JqZXRvIHF1ZSBzZSBjcmVhIGVuIGVsbGFzLCBvIHNpIGV4cGxpY2l0YW1lbnRlIHNlIHV0aWxpemEgZWwgY29tYW5kbyBgcmV0dXJuKClgCgoKIyBQVVJSUl5bYmFzYWRvIGVuIGh0dHBzOi8vamVubnliYy5naXRodWIuaW8vcHVycnItdHV0b3JpYWwvbHMwM19tYXAtZnVuY3Rpb24tc3ludGF4Lmh0bWxdCgpNQVAgZXMgbGEgZm9ybWEgX3RpZHlfIGRlIGhhY2VyIGxvb3BzLiBBZGVtw6FzIGRlIHNlciBtw6FzIHByb2xpam8gZWwgY8OzZGlnbywgZXMgbXVjaG8gbcOhcyBlZmljaWVudGUuCgpMYSBmdW5jacOzbiBfX21hcF9fIHRvbWEgdW4gaW5wdXQsIHVuYSBmdW5jacOzbiBwYXJhIGFwbGljYXIsIHkgYWxndW5hIG90cmEgY29zYSAocG9yIGVqZW1wbG8gcGFyYW1ldHJvcyBxdWUgbmVjZXNpdGUgbGEgZnVuY2nDs24pCgotIG1hcCgueCwgLmYsIC4uLikKLSBtYXAoVkVDVE9SX09fTElTVF9JTlBVVCwgRlVOQ1RJT05fQV9BUExJQ0FSLCBPVFJPU19PUENJT05BTEVTKQoKClVzYW1vcyBfX21hcDJfXyBjdWFuZG8gdGVuZW1vcyBxdWUgcGFzYXIgZG9zIGlucHV0LCBxdWUgc2UgYXBsaWNhbiBzb2JyZSB1bmEgZnVuY2nDs246CgotIG1hcDIoLngsIC55LCAuZiwgLi4uKQotIG1hcDIoSU5QVVRfVU5PLCBJTlBVVF9ET1MsIEZVTkNUSU9OX0FfQVBMSUNBUiwgT1RST1NfT1BDSU9OQUxFUykKClNpIHRlbmVtb3MgbcOhcyBkZSBkb3MuLi4KCi0gcG1hcCgubCwgLmYsIC4uLikKLSBwbWFwKFZFQ1RPUl9PX0xJU1RfSU5QVVQsIEZVTkNUSU9OX0FfQVBMSUNBUiwgT1RST1NfT1BDSU9OQUxFUykKCgpQb3IgZWplbXBsby4gU2kgcXVlcmVtb3MgdXRpbGl6YXIgbGEgZnVuY2nDs24gcHJ1ZWJhIHNvYnJlIGxvcyBkYXRvcyBkZWwgZGF0YWZyYW1lIEFCQ18xMjMKCgoKCmBgYHtyfQpBQkNfMTIzIDwtIGRhdGEuZnJhbWUoTGV0cmFzID0gTEVUVEVSU1sxOjIwXSxOdW0gPSAxOjIwKQpmdW5jaW9uX3BydWViYQpgYGAKClNpIGVsIHJlc3VsdGFkbyBxdWUgcXVlcmVtb3MgZXMgcXVlIGp1bnRlIGNhZGEgZmlsYSwgbmVjZXNpdGFtb3MgcGFzYXJsZSBkb3MgcGFyw6FtZXRyb3M6IHV0aWxpemFtb3MgYG1hcDIoKWAKCgpgYGB7cn0KcmVzdWx0YWRvIDwtIG1hcDIoQUJDXzEyMyRMZXRyYXMsQUJDXzEyMyROdW0sZnVuY2lvbl9wcnVlYmEpCnJlc3VsdGFkb1sxOjNdCgpgYGAKCkxhIHNhbGlkYSBkZSBsb3MgYG1hcCgpYCBlcyB1bmEgX19saXN0YV9fLCBubyB1biB2ZWN0b3IsIHBvciBsbyBxdWUgc2kgbG8gbWV0ZW1vcyBkZW50cm8gZGUgdW4gZGF0YWZyYW1lIHNlIHZlcsOtYSBhc8OtOgoKYGBge3J9CkFCQ18xMjMgJT4lIAogIG11dGF0ZShyZXN1bHRhZG89IG1hcDIoTGV0cmFzLE51bSxmdW5jaW9uX3BydWViYSkpCgpgYGAKCmFsIHBvbmVybG8gZGVudHJvIGRlbCBkYXRhZnJhbWUgZGVzYXJtYSBsYSBsaXN0YSB5IGd1YXJkYSBjYWRhIGVsZW1lbnRvIHBvciBzZXBhcmFkby4KTGEgbWFnaWEgZGUgZXNvIGVzIHF1ZSBwb2RlbW9zIF9fZ3VhcmRhciBjdWFscXVpZXIgY29zYSBlbiBlbCBkYXRhZnJhbWVfXyBubyBzw7NsbyB2YWxvcmVzLCBzaW5vIHRhbWJpw6luIGxpc3RhcywgZnVuY2lvbmVzLCBkYXRhZnJhbWVzLCBldGMuCgpTaSBxdWVyZW1vcyByZWN1cGVyYXIgbG9zIHZhbG9yZXMgb3JpZ2luYWxlcyBlbiBlc3RlIGNhc28gcG9kZW1vcyB1c2FyIGB1bmxpc3QoKWAKCgpgYGB7cn0KCnJlc3VsdGFkb1sxOjNdICU+JSB1bmxpc3QoKQoKQUJDXzEyMyAlPiUgCiAgbXV0YXRlKHJlc3VsdGFkbz0gdW5saXN0KG1hcDIoTGV0cmFzLE51bSxmdW5jaW9uX3BydWViYSkpKQoKYGBgCgoKU2kgbG8gcXVlIHF1ZXLDrWFtb3MgZXJhIHF1ZSBsYSBmdW5jacOzbiBub3MgaGFnYSB0b2RhcyBsYXMgY29tYmluYWNpb25lcyBkZSBsZXRyYXMgeSBuw7ptZXJvLCBlbnRvbmNlcyBsbyBxdWUgbmVjZXNpdGFtb3MgZXMgcGFzYXJsZSBlbCBzZWfDum5kbyBwYXJhbWV0cm8gY29tbyBhbGdvIF9maWpvXywgcG9uaWVuZG9sbyBkZXNwdcOpcyBkZSBsYSBmdW5jacOzbi4gCgpgYGB7cn0KbWFwKEFCQ18xMjMkTGV0cmFzLGZ1bmNpb25fcHJ1ZWJhLEFCQ18xMjMkTnVtKVsxOjJdCmBgYAoKRW4gZXN0ZSBjYXNvLCBlbCBtYXAgaXRlcmEgc29icmUgY2FkYSBlbGVtZW50byBkZSBgbGV0cmFzYCwgeSBwYXJhIGNhZGEgZWxlbWVudG8gX2lfIGhhY2UgCmBmdW5jaW9uX3BydWViYShpLEFCQyROdW0pYCB5IGd1YXJkYSBlbCByZXN1bHRhZG8gZW4gbGEgbGlzdGEKCnNpIGxvIHF1ZXJlbW9zIG1ldGVyIGVuIGVsIGRhdGFmcmFtZQoKYGBge3J9CkFCQ18xMjMgJT4lIAogIG11dGF0ZShyZXN1bHRhZG89IG1hcChMZXRyYXMsZnVuY2lvbl9wcnVlYmEsTnVtKSkKYGBgCgpBaG9yYSBjYWRhIGZpbGEgdGllbmUgdW4gdmVjdG9yIGRlIDIwIGVsZW1lbnRvcyBndWFyZGFkbyBlbiBsYSBjb2x1bW5hIHJlc3VsdGFkbwoKCgojIyBJdGVyYW5kbyBlbiBsYSBFUEgKCkxvIHByaW1lcm8gcXVlIG5lY2VzaXRhbW9zIGVzIGRlZmluaXIgdW4gdmVjdG9yIG8gbGlzdGEgc29icmUgZWwgcXVlIGl0ZXJhci4gCgpQb3IgZWplbXBsbywgcG9kZW1vcyBhcm1hciB1biB2ZWN0b3IgY29uIGxvcyBwYXRoIGEgbGFzIGJhc2VzIGluZGl2aWR1YWxlcywgY29uIGVsIGNvbWFuZG8gYGZzOjpkaXJfbHNgCgpgYGB7cn0KCmJhc2VzX2luZGl2aWR1YWxlc19wYXRoIDwtIGRpcl9scyhwYXRoID0gJy4uL0Z1ZW50ZXMvJywgcmVnZXhwPSAnaW5kaXZpZHVhbCcpCmJhc2VzX2luZGl2aWR1YWxlc19wYXRoCmBgYAoKTHVlZ28sIGNvbW8gZW4gbGEgZnVuY2nDs24gcXVlIHVzYW1vcyBwYXJhIGxlZXIgbGFzIGJhc2VzIGRlZmluaW1vcyBtdWNob3MgcGFyYW1ldHJvcywgbm9zIHBvZGVtb3MgYXJtYXIgdW5hIGZ1bmNpw7NuIF93cmFwcGVyXyBxdWUgc8OzbG8gbmVjZXNpdGUgdW4gcGFyw6FtZXRybywgeSBxdWUgc2ltcGxpZmlxdWUgbGEgZXNjcml0dXJhIGRlbCBtYXAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpsZWVyX2Jhc2VfZXBoIDwtIGZ1bmN0aW9uKHBhdGgpIHsKICByZWFkLnRhYmxlKHBhdGgsc2VwPSI7IiwgZGVjPSIsIiwgaGVhZGVyID0gVFJVRSwgZmlsbCA9IFRSVUUpICU+JSAKICAgIHNlbGVjdChBTk80LFRSSU1FU1RSRSxSRUdJT04sUDIxLENIMDQsIENIMDYpCn0KCmJhc2VzX2RmIDwtIHRpYmJsZShiYXNlc19pbmRpdmlkdWFsZXNfcGF0aCkgJT4lCiAgbXV0YXRlKGJhc2UgPSBtYXAoYmFzZXNfaW5kaXZpZHVhbGVzX3BhdGgsIGxlZXJfYmFzZV9lcGgpKQoKYGBgCgpgYGB7cn0KYmFzZXNfZGYKYGBgCgpFbCByZXN1bHRhZG8gZXMgdW4gREYgZG9uZGUgbGEgY29sdW1uYSBfX2Jhc2VfXyB0aWVuZSBlbiBjYWRhIGZpbGEsIG90cm8gREYgY29uIGxhIGJhc2UgZGUgbGEgRVBIIGRlIGVzZSBwZXLDrW9kby4gRXN0byBlcyBsbyBxdWUgbGxhbWFtb3MgdW4gX25lc3RlZCBERl8gbyBkYXRhZnJhbWUgbmVzdGVhZG8gcGEgbGVzIHBpYmVzLgoKU2kgcXVlcmVtb3MganVudGFyIHRvZG8sIHBvZGVtb3MgdXNhciBgdW5uZXN0KClgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpiYXNlc19kZiA8LSBiYXNlc19kZiAlPiUgdW5uZXN0KCkKYmFzZXNfZGYKYGBgCgo+IMK/UXXDqSBwYXNhIHNpIGxvcyBERiBxdWUgdGVuZW1vcyBuZXN0ZWFkb3Mgbm8gdGllbmVuIGxhIG1pc21hIGNhbnRpZGFkIGRlIGNvbHVtbmFzPwoKCkVzdG8gbWlzbW8gbG8gcG9kZW1vcyB1c2FyIHBhcmEgZnJhZ21lbnRhciBlbCBkYXRhc3RldCBwb3IgYWxndW5hIHZhcmlhYmxlLCBjb24gZWwgYGdyb3VwX2J5KClgCgoKYGBge3J9CmJhc2VzX2RmICU+JSAKICBncm91cF9ieShSRUdJT04pICU+JSAKICBuZXN0KCkKCmBgYAoKQXPDrSwgcGFyYSBjYWRhIHJlZ2nDs24gdGVuZW1vcyB1biBERi4KCj4gwr8gRGUgcXXDqSBzaXJ2ZSB0b2RvIGVzdG8/CgpObyB0b2RvIGVuIGxhIHZpZGEgZXMgdW4gRGF0YWZyYW1lLiBIYXkgZXN0dWN0dXJhcyBkZSBkYXRvcyBxdWUgbm8gc2UgcHVlZGVuIG5vcm1hbGl6YXIgYSBmaWxhcyB5IGNvbHVtbmFzLiBFbiBlc29zIGNhc29zIHJlY3VycsOtYW1vcyB0cmFkaWNpb25hbG1lbnRlIGEgbG9zIGxvb3BzLiBDb24gTUFQIHBvZGVtb3MgdGVuZXIgbG9zIGVsZW1lbnRvcyBhZ3J1cGFkb3MgZW4gdW4gc8OzbG8gb2JqZXRvIHkgYcO6biBjb25zZXJ2YXIgc3VzIGZvcm1hcyBkaWZlcmVudGVzLgoKIyMgRWplbXBsby4gUmVncmVzacOzbiBsaW5lYWwKClNpIGJpZW4gbm8gbm9zIHZhbW9zIGEgbWV0ZXIgZW4gZWwgZGV0YWxsZSBkZWwgbW9kZWxvIGxpbmVhbCwgZXMgw7p0aWwgdXNhcmxvIGNvbW8gZWplbXBsbyBkZSBsbyBxdWUgcG9kZW1vcyBoYWNlciBjb24gTUFQLgoKUGxhbnRlYW1vcyBlbCBtb2RlbG8gCiQkClAyMSA9IFxiZXRhXzAgKyBcYmV0YV8xKkNIMDQgKyBcYmV0YV8yKkNIMDYKJCQKT3NlYSwgdW4gbW9kbGVvIHF1ZSBleHBsaWNhIGVsIGluZ3Jlc28gc2Vnw7puIHNleG8geSBlZGFkCgoKYGBge3J9CgpsbWZpdCA8LSBsbShQMjF+ZmFjdG9yKENIMDQpK0NIMDYsZGF0YSA9IGJhc2VzX2RmKQoKc3VtbWFyeShsbWZpdCkKYGBgCihhbCBmaW5hbCBkZSBsYSBjbGFzZSBwb2RlbW9zIGNoYXJsYXIgc29icmUgbG9zIHJlc3VsdGFkb3MsIHNpIGhheSBpbnRlcsOpcyA6LSkgKQoKRGUgZm9ybWEgVGlkeSwgbGEgbGlicmVyw61hIGBicm9vbWAgbm9zIGRhIGxvcyByZXN1bHRhZG9zIGVuIHVuIERGLgoKYGBge3J9CmJyb29tOjp0aWR5KGxtZml0KQpgYGAKIAogU2kgbG8gcXVlcmVtb3MgaGFjZXIgcG9yIHJlZ2lvbgogCiAKIyMjIExvb3BlYW5kbwoKYGBge3J9CgpyZXN1bHRhZG9zIDwtIHRpYmJsZSgpCgpmb3IgKHJlZ2lvbiBpbiB1bmlxdWUoYmFzZXNfZGYkUkVHSU9OKSkgewogIAogIGRhdGEgPC0gYmFzZXNfZGYgJT4lIAogICAgZmlsdGVyKFJFR0lPTj09cmVnaW9uKQogIAogIGxtZml0IDwtIGxtKFAyMX5mYWN0b3IoQ0gwNCkrQ0gwNixkYXRhID0gZGF0YSkKICAKICBsbXRpZHkgPC0gYnJvb206OnRpZHkobG1maXQpCiAgbG10aWR5JHJlZ2lvbiA8LSByZWdpb24KICByZXN1bHRhZG9zIDwtIGJpbmRfcm93cyhyZXN1bHRhZG9zLGxtdGlkeSkKCn0KCnJlc3VsdGFkb3MKYGBgCgojIyBVc2FuZG8gTUFQCgpQcmltZXJvIG1lIGFybW8gdW5hIGZ1bmNpb24gcXVlIG1lIHNpbXBsaWZpY2EgZWwgY29kaWdvCmBgYHtyfQpmdW48LWZ1bmN0aW9uKHBvcmNpb24sZ3J1cG8pIHsgIGJyb29tOjp0aWR5KGxtKFAyMX5mYWN0b3IoQ0gwNCkrQ0gwNixkYXRhID0gcG9yY2lvbikpfQpgYGAKCmBgYHtyfQpiYXNlc19kZl9sbSA8LSBiYXNlc19kZiAlPiUgCiAgZ3JvdXBfYnkoUkVHSU9OKSAlPiUKICBuZXN0KCkgJT4lIAogIG11dGF0ZShsbSA9IG1hcChkYXRhLGZ1bikpCmJhc2VzX2RmX2xtCmJhc2VzX2RmX2xtICU+JSAKICB1bm5lc3QobG0pCmBgYAoKTyBpbmNsdXNvIG3DoXMgZmFjaWwsIHV0aWxpemFuZG8gYGdyb3VwX21vZGlmeWAgKHF1ZSBlcyB1biBhdGFqbyBxdWUgc29sbyBhY2VwdGEgREYpCgoKCmBgYHtyfQpiYXNlc19kZiAlPiUgCiAgZ3JvdXBfYnkoUkVHSU9OKSAlPiUgCiAgZ3JvdXBfbW9kaWZ5KGZ1bikKYGBgCgoKUGVybyBNQVAgc2lydmUgcGFyYSBvcGVyYXIgY29uIGN1YWxxdWllciBvYmpldG8gZGUgUi4KClBvciBlamVtcGxvIHBvZGVtb3MgZ3VhcmRhciBlbCBfX29iamV0b19fIGBTMzpsbWAgcXVlIGVzIGxhIHJlZ3Jlc2lvbiBsaW5lYWwgZW50cmVuYWRhLiBFc2Ugb2JqZXRvIG5vIGVzIG5pIHVuIHZlY3RvciwgbmkgdW5hIGxpc3RhLCBuaSB1biBERi4gTm8gZXMgdW5hIGVzdHJ1Y3R1cmEgZGUgZGF0b3MsIHNpbm8gcXVlIGVzIGFsZ28gZGlzdGludG8sIGNvbiBfcHJvcGllZGFkZXNfIGNvbW8gYHByZWRpY3QoKWAgcGFyYSBwcmVkZWNpciwgZWwgYHN1bW1hcnkoKWAgcXVlIHZpbW9zLCBldGMuCgpgYGB7cn0KZnVuPC1mdW5jdGlvbihwb3JjaW9uLGdydXBvKSB7ICBsbShQMjF+ZmFjdG9yKENIMDQpK0NIMDYsZGF0YSA9IHBvcmNpb24pfQoKYmFzZXNfZGYgJT4lIAogIGdyb3VwX2J5KFJFR0lPTikgJT4lCiAgbmVzdCgpICU+JSAgCiAgbXV0YXRlKGxtID0gbWFwKGRhdGEsZnVuKSkKYGBgCgoKIyMgV2FsawoKTGFzIGZ1bmNpb25lcyBgV2Fsa2AgVGllbmVuIGxhIG1pc21hIGZvcm1hIHF1ZSBsb3MgYG1hcGAsIHBlcm8gc2UgdXNhbiBjdWFuZG8gbG8gcXVlIHF1ZXJlbW9zIGl0ZXJhciBubyBnZW5lcmEgdW5hIHNhbGlkYSwgc2lubyBxdWUgbm9zIGludGVyZXNhbiBsb3MgZWZlY3RvcyBzZWN1bmRhcmlvcyBxdWUgZ2VuZXJhbi4KCmBgYHtyfQptYXAyKEFCQ18xMjMkTGV0cmFzLEFCQ18xMjMkTnVtLGZ1bmNpb25fcHJ1ZWJhKVsxOjNdCndhbGsyKEFCQ18xMjMkTGV0cmFzLEFCQ18xMjMkTnVtLGZ1bmNpb25fcHJ1ZWJhKQoKYGBgCgpgYGB7cn0KCmltcHJpbWlyX3NhbGlkYSA8LSBmdW5jdGlvbih4LHkpewogIHByaW50KGZ1bmNpb25fcHJ1ZWJhKHgseSkpCn0KCndhbGsyKEFCQ18xMjMkTGV0cmFzLEFCQ18xMjMkTnVtLGltcHJpbWlyX3NhbGlkYSkKYGBgCgpFc28gcXVlIHZlbW9zIGVzIGVsIGVmZWN0byBzZWN1bmRhcmlvIGRlbnRybyBkZSBsYSBmdW5jacOzbiAoaW1wcmltaXIpCgojIyBEaXNjdXNpw7NuLgoKPiBDdWFuZG8gdXNhciBlc3RhcyBoZXJyYW1pZW50YXM/CgpBIGxvIGxhcmdvIGRlbCBjdXJzbyB2aW1vcyBkaWZlcmVudGVzIHTDqWNuaWNhcyBwYXJhIG1hbmlwdWxhY2nDs24gZGUgZGF0b3MuIEVuIHBhcnRpY3VsYXIsIGxhIGxpYnJlcsOtYSBkcGx5ciBub3MgcGVybWl0w61hIGbDoWNpbG1lbnRlIG1vZGlmaWNhciB5IGNyZWFyIG51ZXZhcyB2YXJpYWJsZXMsIGFncnVwYW5kby4gQ3VhbmRvIHVzYW1vcyBgZHBseXJgIHkgY3VhbmRvIHVzYW1vcyBgcHVycnJgLgoKLSBTaSB0cmFiYWphbW9zIHNvYnJlIHVuIERGIHNpbXBsZSwgc2luIHZhcmlhYmxlcyBhbmlkYWRhcyAobG8gcXVlIGNvbm9jw61hbW9zIGhhc3RhIGhveSkgcG9kZW1vcyB1c2FyIGBkcGx5cmAKLSBTaSBxdWVyZW1vcyB0cmFiYWphciBjb24gREYgYW5pZGFkb3MsIGNvbiBjb3NhcyBxdWUgbm8gc29uIERGLCBvIHNpIGVsIHJlc3VsdGFkbyBkZSBsYSBvcGVyYWNpw7NuIHF1ZSB2YW1vcyBhIHJlYWxpemFyIGEgbml2ZWwgZmlsZSBlcyBhbGdvIGRpc3RpbnRvIGEgdW4gdmFsb3Igw7puaWNvLCBub3MgY29udmllbmUgdXNhciBgbWFwYCB5IGBwdXJycmAKCi0gTGFzIGZ1bmNpb25lcyBgd2Fsa2Agc29uIMO6dGlsZXMgcG9yIGVqZW1wbG8gcGFyYSBlc2NyaWJpciBhcmNoaXZvcyBlbiBkaXNjbyBkZSBmb3JtYSBpdGVyYXRpdmEuIEFsZ28gcXVlIG5vIGdlbmVyYSB1bmEgc2FsaWRhCgojIExlY3R1cmEgeSBlc2NyaXR1cmEgZGUgYXJjaGl2b3MgaW50ZXJtZWRpYQpSIHRpZW5lIGZvcm1hdG9zIGRlIGFyY2hpdm9zIHByb3Bpb3M6CgotIFJkYXRhCi0gUkRTCgojIyBSRGF0YQpgYGB7cn0KeCA8LSAxOjE1CnkgPC0gbGlzdChhID0gMSwgYiA9IFRSVUUsIGMgPSAib29wcyIpCgojUGFyYSBndWFyZGFyCnNhdmUoeCwgeSwgZmlsZSA9ICJ4eS5SRGF0YSIpCgojUGFyYSBsZWVyCmxvYWQoJ3h5LlJEYXRhJykKYGBgCgpMb3MgYXJjaGl2b3MgZGUgdGlwbyBfX1JEYXRhX18gcGVybWl0ZW4gZ3JhYmFyIHVuYSBfaW1hZ2VuXyBkZSB0b2RvcyBsb3Mgb2JqZXRvcyBSIHF1ZSBxdWVycmFtb3MuCgojIyBSRFMKCmBgYHtyfQp4CnNhdmVSRFMoeCwgInguUkRTIikKClogPC0gcmVhZFJEUygieC5SRFMiKQpaCmBgYAoKTG9zIGFyY2hpdm9zIGRlIHRpcG8gX19SRFNfXyBubyBndWFyZGFuIGVsIG5vbWJyZSBkZWwgb2JqZXRvLCBwb3IgbG8gcXVlIHBvZGVtb3Mgbm9tYnJhcmxvcyBjdWFuZG8gbG9zIGNhcmdhbW9zIChhY29uc2VqYWJsZSkKCiMjIFNQU1MsIFNUQVRBLCBTQVMKCkEgc3UgdmV6LCB0YW1iacOpbiBSIGZhY2lsbWVudGUgcGVybWl0ZSBsYSBjb211bmljYWNpw7NuIGNvbiBvdHJvcyBzb2Z0d2FyZXMgZXN0YWTDrXN0aWNvcywgbyBwcm9ncmFtYXMgZGVzdGluYWRvcyBhbCBtYW5lam8gZGUgYmFzZXMgZGUgZGF0b3MuIExhIGxpYnJlcsOtYSBfaGF2ZW5fIHRpZW5lIGZ1bmNpb25lcyBxdWUgcGVybWl0ZW4gbGVlciBhcmNoaXZvcyBxdWUgcHJvdmllbmVuIGRlIG90cm9zIHByb2dyYW1hcyBjb21vIF9fU1BTU19fLCBfX1NUQVRBX18geSBfX1NBU19fLCBlbnRyZSBvdHJvcy4gTG9zIGNvbWFuZG9zIHNvbjoKCi0gcmVhZF9zcHNzKCkKLSByZWFkX2R0YSgpCi0gcmVhZF9zYXMoKSAKCiMjIEVuY29kaW5nClRhbnRvIGEgbGEgaG9yYSBkZSBsZWVyIHkgZXNjcmliaXIgYXJjaGl2b3MsIGNvbW8gYWwgdHJhYmFqYXIgdW4gbWlzbW8gc2NyaXB0IGRlc2RlIGRpc3RpbnRhcyBjb21wdXRhZG9yYXMsIGRlYmVtb3Mgc2VyIGN1aWRhZG9zb3MgY29uIGVsIF9lbmNvZGluZ18gc2V0ZWFkby4gRWwgX2VuY29kaW5nXyBlcyBlbCBzaXN0ZW1hIG1lZGlhbnRlIGVsIGN1YWwgZWwgc2lzdGVtYSBpbnRlcnByZXRhIGxvcyBjYXJhY3RlcmVzIGRlbCBsZW5ndWFqZSBuYXR1cmFsLiBIYXkgbXVjaG9zIF9lbmNvZGluZ3NfIGRpZmVyZW50ZXMsIHF1ZSBpbnRlcnByZXRhbiBkaXN0aW50byBhbGd1bm9zIGNhcmFjdGVyZXMsIGNvbW8gdGlsZGVzIHkgc2lnbm9zIGRlIHB1bnR1YWNpw7NuLiAgICAgClBvciBlbmRlLCBzaSBlbCBfZW5jb2RpbmdfIHNldGVhZG8gbm8gZXMgZWwgbWlzbW8gcXVlIGVsIGRlIG51ZXN0cm8gc2NyaXB0L3RhYmxhIHB1ZWRlbiBnZW5lcmFyc2UgZXJyb3Jlcy4gRW4gbWVkaWRhIGRlIGxvIHBvc2libGUsIGFsIGVzY3JpYmlyIG51ZXN0cm9zIHNjcmlwdHMgZXMgcmVjb21lbmRhYmxlIGV2aXRhciBlc3RvcyBjYXJhY3RlcmVzLiAKClIgdGllbmUgcG9yIGRlZmF1bHQgZWwgZW5jb2RpbmcgX18iSVNPLTg4NTktMSJfXywgc2luIGVtYmFyZ28gZWwgbcOhcyBoYWJpdHVhbCBlbiBBbcOpcmljYSBMYXRpbmEgZXMgX18iVVRGLTgiX18uIAoKLSAqKkxlY3R1cmEgZGUgYXJjaGl2b3MqKiA6IEFnbHVuYXMgZGUgbGFzIGZ1bmNpb25lcyBkZWwgdGlwbyBfX3JlYWRfdGFibGVfXywgX19yZWFkLnhsc3hfXyBwZXJtaXRlbiBlc3RhYmxlY2VyIGNvbW8gdW5vIGRlIHN1cyBwYXJhbWV0cm9zIGVsIF9lbmNvZGluZ18gZGVzZWFkbyAKLSAqKkVuY29kaW5nIHV0aWxpemFkbyBwYXJhIGFicmlyIHVuIHNjcmlwdCoqOkZpbGUgLT4gUmVvcGVuIHdpdGggRW5jb2RpbmcKLSAqKkVuY29kaW5nIGRlZmF1bHQgY29uIGVsIHF1ZSBzZSBndWFyZGFuIG51ZXN0cm9zIFNjcmlwdHMqKjogVG9vbHMgLT4gR2xvYmFsIE9wdGlvbnMgLT4gQ29kZSAtPiBTYXZpbmcKIyBPcmdhbml6YWNpw7NuIHNjcmlwdHMKClBvciDDumx0aW1vLCBlcyBhY29uc2VqYWJsZSBtYW50ZW5lciBlbiB0b2RvcyBsb3Mgc2NyaXB0IHVuYSBtaXNtYSBlc3RydWN0dXJhIGRlbCB0aXBvOgoKMS4gTGltcGlhciBsYSBtZW1vcmlhIGBgYCBybShsaXN0PWxzKCkpIGBgYCAgICAKMi4gQ2FyZ2FyIGxpYnJlcsOtYXMKNC4gRGVmaW5pciBmdW5jaW9uZXMKNS4gTGV2YW50YXIgYXJjaGl2b3MgICAgIAouLi4gcHJvY2VzYW1pZW50byAuLi4uICAgICAKbi4gZ3JhYmFyIHJlc3VsdGFkb3MKClRhbWJpw6luIGVzIMO6dGlsIG9yZ2FuaXphciBsYXMgcGFydGVzIGRlbCBzY3JpcHQgZW4gY2Fww610dWxvcy4gUGFyYSBlc28gICAKCgpgYGAjIyMgZXNjcmliaW1vcyBlbCB0w610dWxvIGRlbCBjYXBpdHVsbyBlbmNlcnJhZG8gZW50cmUgdHJlcyBvIG3DoXMgbnVtZXJhbGVzICMjI2BgYAoKCiMgQXl1ZGFzCgpIYXkgbXVjaGFzIGF5dWRhcywgcHJvcGlhcyBkZWwgcHJvZ3JhbWEsIG8gZGUgdXN1YXJpb3MsIHF1ZSBwdWVkZW4gc2VyIGRlIGF5dWRhLgoKLSBFbiBlbCBwcm9ncmFtYSwgcGFyYSBjb25zdWx0YXIgbG9zIHBhcsOhbWV0cm9zIGRlIHVuYSBmdW5jacOzbiwgbGUgZXNjcmliZSBgYGA/ZnVuY2lvbigpYGBgCgotIFtSc3R1ZGlvXShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS9yZXNvdXJjZXMvY2hlYXRzaGVldHMvKSB0aWVuZSB1bm9zIG1hY2hldGVzIG11eSDDunRpbGVzCgotIFtSZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnLykKCi0gW3N0YWNrIG92ZXJmbG93XShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy90YWdnZWQvcikgY29udmllbmUgbGxlZ2FyIGRlc2RlIGdvb2dsZQoKTGEgX2NsYXZlXyBlcyBhcHJlbmRlciBsYSB0ZXJtaW5vbG9nw61hIHBhcmEgZ29vZ2xlYXIgZW4gaW5nbGVzIGxhcyBkdWRhcywgeSBwcmVzdGFyIGF0ZW5jacOzbiBhIHF1ZSBsYXMgcmVzcHVlc3RhcyBzZWFuIGFjdHVhbGVzIChSIGVzIHVuIGxlbmd1YWplIF92aXZvXykKCiMgRWplcmNpY2lvcyBwYXJhIHByYWN0aWNhcgotIENyZWFyIHVuYSAqKmZ1bmNpw7NuKiogbGxhbWFkYSBfSG9sYU11bmRvXyBxdWUgaW1wcmltYSBlbCB0ZXh0byAiSG9sYSBtdW5kbyIKLSBDcmVhciB1bmEgKipmdW5jacOzbioqIHF1ZSBkZXZ1ZWx2YSBsYSBzdW1hdG9yaWEgZGUgbG9zIG7Dum1lcm9zIGVudGVyb3MgY29tcHJlbmRpZG9zIGVudHJlIDEgeSB1biBwYXLDoW1ldHJvIF94XyBhIGRlZmluaXIuCgotIENyZWFyIHVuYSAqKmZ1bmNpw7NuKiogcXVlIGNhbGN1bGUgbGEgZnJlY3VlbmNpYSBleHBhbmRpZGEgcG9yIHVuIFBPTkRFUkEKLSBVdGlsaXphciBkaWNoYSBmdW5jacOzbiBwYXJhIGNhbGN1bGFyIGxhIGZyZWN1ZW5jaWEgcG9ibGFjacOzbmFsIHBvciBTZXhvIHkgUmVnacOzbgoKLSBNb2RpZmljYXIgbGEgZnVuY2nDs24gYW50ZXJpb3IgcGFyYSBxdWUgZGV2dWVsdmEgdW4gdmVjdG9yIGNvbiBsYSBmcmVjdWVuY2lhIG11ZXN0cmEgKip5KiogbGEgZnJlY3VlbmNpYSBwb2JsYWNpb25hbAotIFV0aWxpemFyIGxhIGZ1bmNpw7NuIG1vZGlmaWNhZGEgcGFyYSBjYWxjdWxhciBsYSBmcmVjdWVuY2lhcyBmcmVjdWVuY2lhcyBtdWVzdHJhbGVzIHkgcG9ibGFjaW9uYWxlcyBwb3IgU2V4byB5IFJlZ2nDs24KCgotIExldmFudGFyIGxhIGJhc2UgSW5kaXZpZHVhbCBkZWwgMWVyIHRyaW1lc3RyZSBkZSAyMDE3LCBkZSBsYSBFUEgKLSBHdWFyZGFyIGxhIGJhc2UgSW5kaXZpZHVhbCBkZWwgMWVyIHRyaW1lc3RyZSBkZSAyMDE3IGNvbW8gdW4gYXJjaGl2byBkZSBleHRlbnNpw7NuIC5SRFMKLSBWb2x2ZXIgYSBsZXZhbnRhciBsYSBiYXNlLCBwZXJvIGNvbW8gLlJEUyB5IGFzaWduYXJsYSBjb24gZWwgbm9tYnJlIF9CYXNlUkRTXyDCv3RhcmRhIG3DoXMgbyBtZW5vcz8KCgoKCgo=